#!/usr/local/bin/perl -w

package MTRecentImages;

use strict;

use MT;
use MT::Entry;
use MT::Template::Context;
use vars qw( $error $args );

=head1 NAME

MTRecentImages is a plugin for Movable Type
(L<http://www.movabletype.org>) that allows you to pull out a list of
the most recent images from the posts on your weblog.

=head1 SYNOPSIS

  <MTRecentImages num_images="10"> 
    <img src="<$MTRecentImageUrl$>" alt="<$MTRecentImageName$>"
        <$MTRecentImageWidth$>
        <$MTRecentImageHeight$> /> 
    <br/>
  <$MTRecentImageName$>


=head1 DESCRIPTION

MTRecentImages is used to work with the most recent images in the posts
on your weblog.  You could use this to make a list of the most recent
images as a sidebar, an ATOM or RSS feed, or to simply make a gallery
page of you most recent images.

The plugin can also generate thumbnails or small details of the images
it comes across.

UPDATE:  I have changed the processing to include both entry and
extended entry.  I should probably make this a preference, but it should
be ok for now.

MTRecentImages is NOT a full featured gallery script, nor will it become
one.  I have one of those already.  ;)

=head1 INSTALLATION

Place this file in your Movable Type plugins directory, and add
something like the above synopsis to a template of your choosing.

=head1 REQUIREMENTS

This plugin requires Movable Type (obviously) and HTML::TokeParser,
available on the CPAN.  I will look into packaging HTML::TokeParser with
this plugin, but it may be overkill.  If you choose to use the image
thumbnailing, you will also need Image::Magick.  If you have
Image::Magick installed already (hint: check your mt-check.cgi page to
double check.  If you can have MT make thumbnails for you already, then
you have Image::Magick installed.).  

UPDATE:  I have removed the dependency on Image::Magick::Thumbnail.  Since
the module was one subroutine, i have just pulled it in here.  Credit goes 
to Lee Goddard <LGoddard@CPAN.org> for creating Image::Magick::Thumbnail
and for saving me a ton of hassle while I was initially coding this module.

=head1 TAGS AND CONFIGURATION

=head2 Default Number of Images

  use constant NUM_IMAGES => 10;

Set NUM_IMAGES to the number of images that you would like to populate
the tag by default.

=head2 MTRecentImages

MTRecentImages is the main context tag that will allow you to use the
RecentImages plugin.  It works in the same way as MTEntries or
MTCategories, in that it simply sets populates the data for loop you
will use to print out your images.

=head3 MTRecentImages parameters

Parameters that can be passed in to the plugin to make it filter or
exclude certain types of images, or to simply limit the number of images
pulled.

=head4 replace_blank_alt

MT will silently pass over tags that return undefs.  I found this out
while puzzling over why when I requested 10 images I would only get 8. 
Turns out images 9 and 10 did not have ALT tags, which are used for the
names.  For shame, for sure, but this will be a common scenario for many
people.  replace_blank_alt will substitute your given text for an empty
ALT tag.

=head4 offset

This works just like the normal MT offset tag.  An offset of N will skip
the first N images.

=head4 num_images

The maximum number of images to pull out.

=head4 only_category

Only pull images from posts of a certain category. This requires you to
pass in the numeric ID for the category.

=head4 only_category_name

Same as only_category, but you can pass in the name of the category.

=head4 exclude_category

Skip images from the specified category.  This requires you to pass in
the numeric ID for the category.

=head4 exclude_category_name

Same as exclude_category, but you can pass in the name of the category.

=head4 only_width

Only pull images matching the specified width.  NOTE:  to keep the
requirements light for this module, I am relying on the HTML that is
listing the original image to pull the width.  If i get around to it, i
will add some optional real image checking, but it is more complicated
that I feel like dealing with tonight.

=head4 only_height

Only pull images matching the specified height.  See NOTE above.

=head4 only_type

Only pull images matching the specified type.

=head4 exlude_type

Exclude any images matching the specified type.

=head4 thumbnail_dir

If this is set, then thumbnails of the images will be written out to
this directory.  As with any file-writing CGI, your web server will need
write permissions to be able to successfully make the thumbnails.  This
requires the Image::Magick::Thumbnail module.  Will require that other
thumbnail parameters are also set.  See below.

=head4 th_width, th_height

This pair of parameters will determine the maximum size of the
thumbnails for each image.

=head4 detail_dir

If this is set, then small, random details of the images will be written
out to this directory.  These differ from the thumbnails in that they
show a portion of the image at actual size as opposed to a tiny version
of the whole image.  As with any file-writing CGI, your web server will
need write permissions to be able to successfully make the details. 
Will require that other thumbnail parameters are also set. See below.

=head4 det_width, det_height

This pair of parameters will determine the maximum size of the details
for each image.

=head4 TODO: order_by

Sort the list by the specified parameter.

=head4 use_existing_thumbnails

Specifies whether or not to skip generation of thumbnails for files already
on disk.

=head2 Output Tags

=head3 MTRecentImageUrl

MTRecentImageUrl is the source URL for the image in question.  This tag
is only valid in an MTRecentImages block.  As of right now, this will
simply mimic the src tag of the image.  It will not transform a relative
URL into an absolute URL.

=head3 MTRecentImageName

MTRecentImageName is the ALT text for the image in question.  This tag
is only valid in an MTRecentImages block.  I should make another
configuration variable to deal with missing ALT tags.  Perhaps the
default title could be "Shame on you."

=head3 MTRecentImageWidth

MTRecentImageWidth is the width of the image in question.  This tag is
only valid in an MTRecentImages block.  This is useful if you want to
spit out the image on a page, rather than simply linking to it.

=head3 MTRecentImageHeight

MTRecentImageHeight is the height of the image in question.  This tag is
only valid in an MTRecentImages block.  This is useful if you want to
spit out the image on a page, rather than simply linking to it.

=head3 MTRecentImageEntryUrl

MTRecentImageEntryUrl is the permalink of the entry in which the image
was found. This is useful if you want to link to not just the image, but
the source article.

=head3 MTRecentImageEntryTitle

MTRecentImageEntryTitle gives the title of the entry in which the image
was found.

=head3 MTRecentImageEntryText

The text of the entry that the image is in.

=head3 MTRecentImageEntryTextMore

The extended text of the entry that the image is in.

=head3 MTRecentImageEntryTextExcerpt

The excerpt text for the entry that the image is in.

=head3 MTRecentImageThumb, MTRecentImageThumbW, MTRecentImageThumbH

The URL and size of the generated thumbnail, if available.

=head3 MTRecentImageDetail, MTRecentImageDetailW, MTRecentImageDetailH

The URL and size of the generated detail, if available.

=head1 AUTHOR & CONTACT

MTRecentImages is written by Jason Gessner ( jason@multiply.org ) and is
free to use and modify, under the condition that proper credit is given
and that further modifications, should they be released to the public,
be linked to this original as well.  Share and share alike, but play
nicely, please.

Special thanks to Ari Paparo (L<http://www.aripaparo.com/>), who added
the MTRecentImageEntryUrl and MTRecentImageEntryTitle parameters. 
Thanks, Ari!

The most recent version of this plugin will always be found at
L<http://www.multiply.org/perl/mtplugs/>.

=cut

BEGIN {
    eval { require HTML::TokeParser; };
    $error = $@;
}

use vars qw( $VERSION );
$VERSION = '2.1_01';

my $plugin;

eval {
    require MT::Plugin;
    $plugin = MT::Plugin->new({
        name            => 'Recent Images',
        description     => qq{Adds multiple tags to your templates to help you find and 
                            display images from your posts.  Version 2.0 is context
                            aware, so it will automagically limit the image list to 
                            your current entry, category or archive context!},
        doc_link        => 'http://www.multiply.org/perl/mtplugs/MTRecentImages-doc.html',
        author_name     => 'Jason Gessner',
        author_link     => 'http://www.multiply.org/notebook/',
        version         => $VERSION,
        blog_config_template => \&config_template,
        settings        => new MT::PluginSettings([
            ['recentimages_thumbnail_dir',      { Scope => 'blog' }],
            ['recentimages_thumbnail_width',    { Scope => 'blog' }],
            ['recentimages_thumbnail_height',   { Scope => 'blog' }],
            ['recentimages_detail_dir',         { Scope => 'blog' }],
            ['recentimages_detail_width',       { Scope => 'blog' }],
            ['recentimages_detail_height',      { Scope => 'blog' }]
        ])
    });
    MT->add_plugin($plugin);
};

use constant NUM_IMAGES => 10;

# register our template tag (used as <MTRecentImages>)
MT::Template::Context->add_container_tag(RecentImages => \&images);

my %tags = qw(
    RecentImageUrl                  image_url
    RecentImageName                 image_name
    RecentImageWidth                image_width
    RecentImageHeight               image_height
    RecentImageEntryUrl             entry_url
    RecentImageEntryTitle           entry_title
    RecentImageEntryText            entry_text
    RecentImageEntryTextMore        entry_text_more
    RecentImageEntryTextExcerpt     entry_text_excerpt
    RecentImageThumb                thumb_url
    RecentImageThumbW               thumb_w
    RecentImageThumbH               thumb_h
    RecentImageDetail               detail_url
    RecentImageDetailW              detail_w
    RecentImageDetailH              detail_h    
);

# Push our tags into MT's awareness
foreach my $tag ( keys %tags ) {
    MT::Template::Context->add_tag(
        $tag => sub { shift->stash($tags{$tag}) }
    );
}

# our configuration dialog
sub config_template {
    my $tmpl = <<'EOT';
<div class="setting">
    <p class="alert-success-inline">
        NOTE:  All of these settings can be overridden in your individual templates.  In fact, the values you set here will only be used if you do not supply the values in your templates.
    </p>
    <p class="caption">Image Thumbnail Settings</p>
    <div class="label">
        <label for="recentimages_thumbnail_dir">Thumbnail Dir:</label>
    </div>
    <div class="field">
    <p>
        Where should Recent Images save your thumbnails for this blog?
        <input type="text" name="recentimages_thumbnail_dir" id="recentimages_thumbnail_dir" value="<TMPL_VAR NAME="recentimages_thumbnail_dir" ESCAPE="HTML">" size="50" />
    </p>
    </div> 
    <div class="label">
        <label for="recentimages_thumbnail_width">Width:</label>
    </div>
    <div class="field">
    <p>
        <input type="text" name="recentimages_thumbnail_width" id="recentimages_thumbnail_width" value="<TMPL_VAR NAME="recentimages_thumbnail_width" ESCAPE="HTML">" size="5"/>
    </p>
    </div> 
    <div class="label">
        <label for="recentimages_thumbnail_height">Height</label>
    </div>
    <div class="field">
    <p>
        <input type="text" name="recentimages_thumbnail_height" id="recentimages_thumbnail_height" value="<TMPL_VAR NAME="recentimages_thumbnail_height" ESCAPE="HTML">" size="5"/>
    </p>
    </div> 

    <br clear="all" />
    <p class="caption">Image Detail Settings</p>
    <div class="label">
        <label for="recentimages_detail_dir">Details Dir:</label>
    </div>
    <div class="field">
    <p>
        Where should Recent Images save your thumbnails for this blog?
        <input type="text" name="recentimages_detail_dir" id="recentimages_detail_dir" value="<TMPL_VAR NAME="recentimages_detail_dir" ESCAPE="HTML">" size="50" />
    </p>
    </div> 
    <div class="label">
        <label for="recentimages_detail_width">Width:</label>
    </div>
    <div class="field">
    <p>
        <input type="text" name="recentimages_detail_width" id="recentimages_detail_width" value="<TMPL_VAR NAME="recentimages_detail_width" ESCAPE="HTML">" size="5"/>
    </p>
    </div> 
    <div class="label">
        <label for="recentimages_detail_height">Height</label>
    </div>
    <div class="field">
    <p>
        <input type="text" name="recentimages_detail_height" id="recentimages_detail_height" value="<TMPL_VAR NAME="recentimages_detail_height" ESCAPE="HTML">" size="5"/>
    </p>
    </div> 
</div>
EOT
}

sub config {
    my $config = {};
    
    if ($plugin) {
        require MT::Request;
        my ($scope) = (@_);
        $config = MT::Request->instance->cache('recentimages_config_'.$scope);
        if (!$config) {
            $config = $plugin->get_config_hash($scope);
            MT::Request->instance->cache('recentimages_config_'.$scope, $config);
        }
    }
    return $config;
}

sub images {
    my ($ctx, $args) = @_;

    if ($error) {
        return $ctx->error('Error with <MTRecentImages>: required modules not installed');
    }

    # parameters passed into the plugins
    # configuration - what entries and types of images should we look for?
    # TODO:  make a config object to pass this around with...
    my $use_context             = $args->{use_context};
    if ( !defined($args->{use_context}) ) {
        $use_context = 1;
    }
    my $num_images              = $args->{num_images} || NUM_IMAGES;
    my $offset                  = $args->{offset} || 0;
    my $replace_blank_alt       = $args->{replace_blank_alt} || undef;
    my $only_category           = $args->{only_category} || 0;
    my $only_category_name      = $args->{only_category_name} || undef;
    my $exclude_category        = $args->{exclude_category} || 0;
    my $exclude_category_name   = $args->{exclude_category_name} || undef;
    my $only_type               = $args->{only_type} || undef;
    my $exclude_type            = $args->{exclude_type} || undef;
    my $only_width              = $args->{only_width} || undef;
    my $only_height             = $args->{only_height} || undef;
    my $use_existing_thumbnails = $args->{use_existing_thumbnails} || 0;

    # several settings may be specified through the plugin settings 
    # dialog in the interface on a per blog basis
    my $config = config('blog:'.$ctx->stash('blog_id'));

    # storage location and image resizing options
    my $thumb_dir   = $args->{thumbnail_dir} || $config->{'recentimages_thumbnail_dir'};
    my $thumb_w     = $args->{th_width}  || $config->{'recentimages_thumbnail_width'};
    my $thumb_h     = $args->{th_height} || $config->{'recentimages_thumbnail_height'};
    my $detail_dir  = $args->{detail_dir} || $config->{'recentimages_detail_dir'};
    my $detail_w    = $args->{det_width} || $config->{'recentimages_detail_width'};
    my $detail_h    = $args->{det_height} || $config->{'recentimages_detail_height'};

    
    # configuration options not passed in to the tag
    my $use_date_range          = 0;
    my $only_entry_id           = 0;

    my $blog_id = $ctx->stash('blog_id');
    my $blog    = MT::Blog->load( $blog_id );

    my $current_entry = $ctx->stash('entry');
    if ( $use_context && $current_entry ) { 
        $only_entry_id = $current_entry->id();

    } elsif ( $use_context && $ctx->{current_archive_type} ) {
        if ( $ctx->{current_archive_type} eq 'Category' ) {
            # get the current category and set it as the only_category...
            my $archive_category = $ctx->stash('archive_category');
            $only_category = $archive_category->id();
            
        } elsif ( $ctx->{current_archive_type} eq 'Monthly' ) {
            $use_date_range = 1;

        }
    } elsif ( $ctx->{inside_mt_categories} ) {
        # get the current category and set it as the only_category...
        my $category        = $ctx->stash('category');
        $only_category      = $category->id();
        $only_category_name = $category->name();
    }

    # if we are passed in the name of a category to include or exclude, 
    # we need to get it's ID.
    if ( $only_category_name && !$only_category ) {
        my $only_cat_iter = MT::Category->load_iter({ 
            blog_id => $blog_id, 
            label   => $only_category_name
        });
        if ( my $only_cat = $only_cat_iter->() ) {
            # this trumps the numeric ID
            $only_category = $only_cat->id;
        }
    }
    
    if ( $exclude_category_name && !$exclude_category ) {
        my $exclude_cat_iter = MT::Category->load_iter({ 
            blog_id => $blog_id,
            label   => $exclude_category_name 
        });
        if ( my $exclude_cat = $exclude_cat_iter->() ) {
            # this trumps the numeric ID
            $exclude_category = $exclude_cat->id;
        }
    }

    my $site_url  = $blog->site_url;
    my $site_path = $blog->site_path;

    my $iter;
  
    # clean up our incoming arguments for resizing
    if ( $thumb_w && !$args->{th_height} ) {
        $thumb_h = undef;
    } elsif ( $thumb_h && !$args->{th_width} ) {
        $thumb_w = undef;
    }
    
    # TODO move the iterator creation to a subroutine
    if ( $only_entry_id ) {
        $iter = MT::Entry->load_iter({ 
            blog_id => $blog_id, 
            id      => $only_entry_id,
            status  => 2 # publish.  TODO make a constant for this or use an MT constant
        },);
     
    } elsif ( $use_date_range ) { 
        $iter = MT::Entry->load_iter(
            { 
                blog_id     => $blog_id, 
                status      => 2, # publish.  TODO make a constant for this or use an MT constant
                created_on  => [$ctx->{current_timestamp}, $ctx->{current_timestamp_end}]
            }, 
            { sort => 'created_on', direction => 'descend', range => { created_on => 1 } }
        );
        
    } else {
        # normal loader
        $iter = MT::Entry->load_iter(
            { 
                blog_id => $blog_id, 
                status  => 2, # publish.  TODO make a constant for this or use an MT constant
            }, 
            { sort => 'created_on', direction => 'descend' }
        );
     
    }

    my @images;

    while (my $entry = $iter->()) {
        # are we using only a single category?
        if ( $only_category ) {
            my $category = $entry->category;
            next unless ( defined($category) && ( $category->id == $only_category ) );
        }
        
        # are we excluding a single category?
        if ( $exclude_category ) {
            my $category = $entry->category;
            next if ( defined($category) && ( $category->id == $exclude_category ) );
        }
       
        my @new_images = extract_images( 
            $entry, $replace_blank_alt, $only_type, $exclude_type, $only_width, 
            $only_height, $thumb_dir, $thumb_w, $thumb_h, 
            $detail_dir, $detail_w, $detail_h, $site_path, $site_url, $use_existing_thumbnails );
       
        if ( !scalar(@new_images) && $error ) {
            return $ctx->error($error);
        }
 
        push @images, @new_images;   
            last if ( scalar( @images ) >= $num_images );
    }

    my $res     = '';
    my $builder = $ctx->stash('builder');
    my $tokens  = $ctx->stash('tokens');

    my $image_num = 0;
    foreach my $image ( @images ) {
        $image_num++;
        next if $image_num <= $offset;
        last if ( $image_num > ( $num_images + $offset ) );

        my @output_attrs = qw(
            image_url image_name image_width image_height 
            entry_url entry_title entry_text entry_text_more 
            entry_text_excerpt thumb_url thumb_w thumb_h
            detail_url detail_h detail_w
        );

        foreach my $attr ( @output_attrs ) {
            $ctx->stash($attr, $image->{$attr});
        }

        my $out = $builder->build($ctx, $tokens);
        
        $res .= $out;
        
    }

    return $res;

}

sub extract_images {
    my ( $entry, $replace_blank_alt, $only_type, $exclude_type, 
         $only_width, $only_height, $thumb_dir, $thumb_w, $thumb_h, 
         $detail_dir, $detail_w, $detail_h, $site_path, $site_url,
         $use_existing_thumbnails ) = @_;

    my $content = MT->apply_text_filters($entry->text . $entry->text_more, $entry->text_filters);
    return unless $content;

    my @found_images;

    my $stream = HTML::TokeParser->new( \$content );

    while (my $token = $stream->get_token ) {
        # TODO make this a next so we don't have this looooooooong block.
        if ( $token->[0] eq 'S' and lc($token->[1]) =~ m/img/i ) {
        my $img_attr = $token->[2]; # the attributes for this image tag.

        # are we matching a certain type of image?
        if ( $only_type ) {
            next unless ( $img_attr->{'src'} =~ m/$only_type$/i)
        }
        # are we excluding a certain type of image?
        if ( $exclude_type ) {
            next if ( $img_attr->{'src'} =~ m/$exclude_type$/i)
        }

        my $image_width  = $img_attr->{'width'}  ? $img_attr->{'width'} : "";
        my $image_height = $img_attr->{'height'} ? $img_attr->{'height'} : "";

        if ( defined($only_width) && defined($only_height) ) {
            next unless ( 
                ($image_width == $only_width) 
                &&
                ($image_height == $only_height) );
        } elsif ( defined($only_width) && !defined($only_height) ) {
            next unless ( ($image_width == $only_width));
        } elsif ( !defined($only_width) && defined($only_height) ) {
             next unless ( ($image_height == $only_height));
        }
            

        my $image_alt = $img_attr->{'alt'};
        if ( $replace_blank_alt && !defined($image_alt) ) {
            $image_alt = $replace_blank_alt;
        }

        # OK, we made it this far, so we are using this image.
        # let's see if we are making a thumbnail for it.
        my ( $thumb_image, $thumb_width, $thumb_height );
        if ( $thumb_dir && -d $thumb_dir ) {
            eval { require Image::Magick; };

            unless ( $@ ) {
                my $local_file = blog_url_to_file_path( $site_url, $site_path, $img_attr->{'src'} );

                if ( $local_file ) {
                    my $filename = filename( $local_file );
                    $thumb_image = file_path_to_blog_url( $site_url, $site_path, "$thumb_dir/$filename" );

            if (-e "$thumb_dir/$filename" && $use_existing_thumbnails && (-M "$thumb_dir/$filename" < -M $local_file) ) {
                # we need to read the existing thumbnail image
                my $existing_thumbnail = new Image::Magick;
                my $read_return = $existing_thumbnail->Read("$thumb_dir/$filename");
                if ( $read_return ) {
                    print STDERR "Returned from Read(): $read_return\n";
                    next;
                }
                ($thumb_width,$thumb_height) = $existing_thumbnail->Get('width','height');
                if ( !($thumb_width || $thumb_height) ) {
                    print STDERR "Couldn't get the width and height for the image.\n";
                    return undef;
                }
     
            } else {
                my $src = new Image::Magick;
                my $read_return = $src->Read($local_file);
                if ( $read_return ) {
                    print STDERR "Returned from Read(): $read_return\n";
                    next;
                }

                my $thumb;
                ($thumb,$thumb_width,$thumb_height) = create_thumbnail($src, $thumb_w, $thumb_h);
                my $x = $src->Write("$thumb_dir/$filename");
                if ( $x ) { $error = "Error writing thumbnail image [$filename]: $x"; return; } 
            }
                }
            }
        }

        my ( $detail_image, $detail_width, $detail_height );
        if ( $detail_dir && -d $detail_dir ) {
            eval { require Image::Magick; };
            unless ( $@ ) {
                my $local_file = blog_url_to_file_path( $site_url, $site_path, $img_attr->{'src'} );

                if ( $local_file ) {
                    my $src = new Image::Magick;
                    my $read_return = $src->Read($local_file);
                    if ( $read_return ) {
                        print STDERR "Returned from Read(): $read_return\n";
                        next;
                    }

                    my ( $src_w, $src_h ) = ( $src->Get('columns'), $src->Get('rows') );
                    if ( $src_w > $detail_w && $src_h > $detail_h ) {
                        my ( $det_x, $det_y ) = ( int(rand($src_w-$detail_w)), int(rand($src_h-$detail_h)) );
                        my $detail_img = new Image::Magick;
                        $src->Crop(
                            geometry=> $detail_w . "x" . $detail_h,
                            x => $det_x,
                            y => $det_y
                        );
                        $detail_height = $detail_h;
                        $detail_width = $detail_w;

                        my $filename = filename( $local_file );
                        my $x =  $src->Write("$detail_dir/$filename");
                if ( $x ) { $error = "Error writing detail image [$filename]: $x"; return; } 
                        $detail_image = file_path_to_blog_url( $site_url, $site_path, "$detail_dir/$filename" );
                    }
                }
            }
        }

        push @found_images, { 
            image_name          => $image_alt, 
            image_url           => $img_attr->{'src'}, 
            image_width         => "width=\"$image_width\"", 
            image_height        => "height=\"$image_height\"", 
            entry_url           => $entry->permalink, 
            entry_title         => $entry->title,
            entry_text          => $entry->text,
            entry_text_more     => $entry->text_more,
            entry_text_excerpt  => $entry->excerpt,
            thumb_url           => $thumb_image,
            thumb_w             => $thumb_width,
            thumb_h             => $thumb_height,
            detail_url          => $detail_image,
            detail_w            => $detail_width,
            detail_h            => $detail_height 
        };
                                
        }
    }
  
    return @found_images;
}

sub blog_url_to_file_path {
    my ( $blog_base, $file_base, $url ) = @_;
                                                                                
    # The blog base should be the URL of the blog (absolute)
    # The file base is the local location of that URL.
    # The url is the URL for the image we are examining.
                                                                                
    unless ( $blog_base =~ m!/$! ) { $blog_base .= "/"; }
    unless ( $file_base =~ m!/$! ) { $file_base .= "/"; }
                                                                                
                                                                                
    # normalize the URL if it is relative.
    unless ( $url =~ m!^http://! ) {
        # URL is relative, so we need to figure this out in relation to our
        # base URL.
        # get our hostname only
        my $domain_name;
        if ( $blog_base =~ m!^(http://)(.*?)/!i ) {
            $domain_name = "$1$2";
        } else {
            # something is wrong with our base URL.
            return undef;
        }
                                                                                
        my $full_path;
        if ( $url =~ m!^/! ) {
            # full relative on current server
            $url = "$domain_name$url";
        } else {
            # relative to current path (we are assuming this is the
            # blog URL, not the archive path.
            # This will probably need to change.
            $url = "$blog_base$url";
        }
    }
                                                                                
    # now that we have normalized our URL, let's translate the
    # full thing to a filepath.
    if ( $url =~ m!^$blog_base!i ) {
        # URL is absolute on our server
        if ( $url =~ s/$blog_base/$file_base/i ) {
            return $url if -e $url;
        } else {
            return undef;
        }
    } else {
        # image is absolute and NOT on our server (at least as far as
        # this blog is concerned.  Return undef;
        return undef;
    }
}
           
sub file_path_to_blog_url {
    my ( $blog_base, $file_base, $url ) = @_;
      
    unless ( $blog_base =~ m!/$! ) { $blog_base .= "/"; }
    unless ( $file_base =~ m!/$! ) { $file_base .= "/"; }
                                                                          
    if ( $url =~ m!^$file_base! ) {
        # URL is absolute on our server
        $url =~ s/$file_base/$blog_base/;
        return $url;
    } else {
        # image is absolute and NOT on our server (at least as far as
        # this blog is concerned.  Return undef;
        return undef;
    }
}

sub filename {
    my $path = shift;

    my $rindex = rindex( $path, "/" );

    if ( $rindex ) {
        return substr( $path, $rindex + 1 );
    } else {
        return undef;
    }
}

sub create_thumbnail {
    my ($img, $width, $height) = (shift, shift, shift);

    if (not $img){
        warn "No image in ".__PACKAGE__."::create";
        return undef;
    }
    if (!($width || $height)){
        warn "No dimensions specified to create_thumbnail.\n";
        return undef;
    }
    my ($ox,$oy) = $img->Get('width','height');
    if ( !($ox || $oy) ) {
        print STDERR "Couldn't get the width and height for the image.\n";
        return undef;
    }
    
    # From geo, get the longest side of the box into which to fit:
    my ($new_width, $new_height);
    if ( $width && $height ) {
        # simple.  the user told us exactly what to do
        $new_width  = $width;
        $new_height = $height;

    } elsif ( $width ) {
        # scale to fit by width
        $new_width  = $width;
        my $scale = $new_width / $ox;
        $new_height = int($oy * $scale);
    
    } elsif ( $height ) {
        # scale to fit by height
        $new_height = $height;
        $new_width  = int($ox / ($oy / $new_height));
    }

    my $create_thumb_result = $img->Scale(width=>$new_width,height=>$new_height);

    return $img, $new_width, $new_height; 
}

1;
